﻿using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;

using WFL.Windows;

namespace WFL.Windows.Forms
{
    public class KeyboardLLHook
    {
        private IntPtr _hHook;
        private Header.Winuser.LowLevelKeyboardProc _keyboardLLProc;

        public event KeyStateEventHandler KeyDown;
        public event KeyStateEventHandler KeyUp;

        public KeyboardLLHook()
        { 
        
        }

        public bool Hook()
        {
            if (_hHook == IntPtr.Zero)
            {
                IntPtr hInstance = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().ManifestModule);
                _keyboardLLProc = new Header.Winuser.LowLevelKeyboardProc(KeyboardLLProc);
                IntPtr result = API.User32.SetWindowsHookEx(Header.Winuser.WH_KEYBOARD_LL,
                    _keyboardLLProc,
                    hInstance,
                    0);
                if (result == IntPtr.Zero)
                {
                    return false;
                }
                _hHook = result;
            }

            return true;
        }

        public bool Unhook()
        {
            if (_hHook != IntPtr.Zero)
            {
                int result = API.User32.UnhookWindowsHookEx(_hHook);
                if (result == 0)
                {
                    return false;
                }
                _keyboardLLProc = null;
                _hHook = IntPtr.Zero;
            }

            return true;
        }

        private IntPtr KeyboardLLProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode == Header.Winuser.HC_ACTION)
            {
                KBDLLHOOKSTRUCT keyInfo = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
                Keys modifiers = GetModifierKeys();
                KeyStateEventArgs e = new KeyStateEventArgs(keyInfo, modifiers);
                if ((keyInfo.flags & Header.Winuser.LLKHF_UP) == Header.Winuser.LLKHF_UP)
                {
                    OnKeyUp(e);
                }
                else
                {
                    OnKeyDown(e);
                }

                if (e.SuppressKeyState)
                {
                    return (IntPtr)1;
                }
                else
                {
                    return API.User32.CallNextHookEx(_hHook, nCode, wParam, lParam);
                }
            }
            else if (nCode < 0)
            {
                return API.User32.CallNextHookEx(_hHook, nCode, wParam, lParam);
            }
            else
            {
                return API.User32.CallNextHookEx(_hHook, nCode, wParam, lParam);
            }
        }

        private Keys GetModifierKeys()
        {
            Keys modifiers = Keys.None;
            if (API.User32.GetAsyncKeyState((int)Keys.ShiftKey) < 0)
            {
                modifiers |= Keys.Shift;
            }
            if (API.User32.GetAsyncKeyState((int)Keys.ControlKey) < 0)
            {
                modifiers |= Keys.Control;
            }
            if (API.User32.GetAsyncKeyState((int)Keys.Menu) < 0)
            {
                modifiers |= Keys.Alt;
            }
            return modifiers;
        }

        private void OnKeyDown(KeyStateEventArgs e)
        {
            if (KeyDown != null)
            {
                KeyDown(this, e);
            }
        }

        private void OnKeyUp(KeyStateEventArgs e)
        {
            if (KeyUp != null)
            {
                KeyUp(this, e);
            }
        }
    }
}
